<section>
<h4>缓存和QuerySet</h4>
<p>每个QuerySet都有一个缓存对象，刚刚创建QuerySet时，该缓存对象为空，当Django第一次执行sql把数据从数据库载入QuerySet后，缓存对象的内容就变为从数据库拉取的数据了。之后发生在这个QuerySet上的查询就会使用缓存的数据。知道了这一特性，有利于我们更好的使用QuerySet。例：</p>
<pre>
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
</pre>
<p>
上面的代码会创建2个QuerySet,执行2次数据库查询(2次访问数据库)
</p>
<pre>
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # 访问数据库并拉取数据
>>> print([p.pub_date for p in queryset]) # 使用缓存的数据
</pre>
<p>何时不缓存QuerySet</p>
<p>使用slice或者数组下标语法来限定只载入QuerySet的一部分数据的情况下不缓存QuerySet。例：</p>
<pre>
>>> queryset = Entry.objects.all()  #没有数据库访问
>>> print(queryset[5]) # 访问数据库
>>> print(queryset[5]) # 再次访问数据库
</pre>
<p>注意，在整个QuerySet已经载入的情况下即使使用下标语法，也会使用缓存</p>
<pre>
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # 载入数据
>>> print(queryset[5]) # 使用缓存
>>> print(queryset[5]) # 使用缓存
</pre>
</section>
<section>
<h4>Q对象与复杂查询</h4>
<p>前面的章节讲到的filter都是and的关系，那么Django如何实现or的关系的查询呢？Django提供了Q对象来实现对or的支持。语法如下:</p>
<pre>
from django.db.models import Q
Q(question__startswith='What')
</pre>
<p>Q对象支持'|'和'&'这样的连接符。例:</p>
<pre>
Q(question__startswith='Who') | Q(question__startswith='What')
</pre>
<p>与之匹配的sql语句为:</p>
<pre>
WHERE question LIKE 'Who%' OR question LIKE 'What%'
</pre>
<p>Q对象还支持'~'操作符，例:</p>
<pre>
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
</pre>
<p>在Django中，所有需要使用关键字参数进行过滤或查询的地方都可以使用Q对象，例:</p>
<pre>
Poll.objects.get(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
</pre>
<p>上面的代码对应的sql语句为</p>
<pre>
SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
</pre>
<p>Django中，Q对象和普通的关键字参数可以混用，但是Q对象作为参数必须写在关键字参数前面, 例：</p>
<pre>
# 这样写ok
Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who',
)  
</pre>
<pre>
# 这样写会出错
Poll.objects.get(
    question__startswith='Who',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
</pre>
</section>
<section>
<h4>对象比较</h4>
<p>djang中支持使用==来判断两个对象是否相等，背后的逻辑是判断两个对象的主键是否相等。例:</p>
<pre>
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id  # 这2行代码是等效的
</pre>
<p>删除对象</p>
<p>在对象上调用delete()方法可以删除对象及其对应的数据库记录, 例</p>
<pre>
>>> e.delete()
(1, {'weblog.Entry': 1})
</pre>
<p>在QuerySet上面调用delete()方法可以批量删除对象</p>
<pre>
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
</pre>
<p>如果在定义model时在关系类型的字段上设置了on_delete=models.CASCADE，那么调用delete()会删除对应的关联对象。例:</p>
<pre>
b = Blog.objects.get(pk=1)
# 会同时删除这个blog关联的entry
b.delete()
</pre>
</section>
<section>
<h4>复制Model实例</h4>
<p>Django提供了非常简单的方法从一个现有的Model实例来创建一个一模一样的新实例(只有pk不同)。将pk或者id设为None，就可以实现复制了</p>
<pre>
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog.save() # blog.pk == 2
</pre>
<p>对于继承(多表继承)得来的Model，情况比上面稍微复杂，需要把pk和id都设为none</p>
<pre>
class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3

django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4
</pre>
<p>注意，这种方式并不会复制关系型字段。例如，对于多对多关系，我们还需要手动的设置ManyToManyField的关联关系</p>
<pre>
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors.set(old_authors)  # 给新的对象手动设置关联关系
</pre>
<p>对于OneToOneField，我们需要手动的复制一对一关系的关联对象，然后重新设置一对一关系</p>
<pre>
detail = EntryDetail.objects.all()[0]

entry = detail.entry  # 手动复制entry
entry.pk = None
entry.save()

detail.pk = None
detail.entry = entry  # 重新设置一对一的关系
detail.save()
</pre>
</section>
<section>
<h4>一次更新多个对象</h4>
<p>Django的QuerySet提供了update()方法来批量更新对象。例:</p>
<pre>
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
</pre>
<p>使用这个方法只能更新所有的非关系类型的字段以及ForeignKey类型的字段.</p>
<p>update这个方法会立刻更新数据库并且返回更新的数据库记录的数目。使用该方法有个限制，就是它只能更新Model对应的表，不能修改关联表。另外， update也支持F()</p>
<pre>
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
</pre>
</section>
<section>
<h4>关联对象(关系对象)</h4>
<p>我们在Model里面定义一个关系类型的字段(ForeignKey, ManyToManyField, OneToOneField), 这个model的实例可以通过这些关系字段方便的访问关联对象，还是以本章开头的Entry和Blod为例，Entry的对象e可以通过e.blog来访问关联的Blog对象, b可以通过Django自动添加了entry_set属性来访问与其关联的Entry对象, b.entry_set.all()</p>
<p><b>One-to-many关系</b></p>
<p>正向关系访问</p>
<p>如果某个Model里面定义了ForeignKey，那么这个Model的实例可以通过该ForeignKey字段来访问其关联的对象</p>
<pre>
>>> e = Entry.objects.get(id=2)
>>> e.blog # 返回关联的blog
</pre>
<p>通过直接给ForeignKey字段赋值来建立关联关系或者修改关联关系</p>
<pre>
>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()
</pre>
<p>如果ForeignKey字段设置了null=True，可以通过把ForeignKey字段设为None来解除关联关系(记得save)</p>
<pre>
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save()
</pre>
<p>对于正向关系的访问，在第一次访问后通常会把关联对象缓存起来，以备后面使用</p>
<pre>
>>> e = Entry.objects.get(id=2) # 访问数据库
>>> print(e.blog)  # 访问数据库，载入关联的blog并把blog缓存
>>> print(e.blog)  # 访问缓存的blog
</pre>
<p>QuerySet提供了select_related()这个方法来直接载入关联对象</p>
<pre>
>>> e = Entry.objects.select_related().get(id=2)  # 访问数据库，并缓存blog
>>> print(e.blog)  # 不访问数据库
>>> print(e.blog)  # 不访问数据库
</pre>
<p><b>反向关系查询</b></p>
<p>如果一个Model里面定义了一个ForeignKey，那么这个ForeignKey指向的Model会有一个Manager，通过这个manager能访问到定义了ForeignKey的这个Model的所有实例。这个manager的名字为FOO_set，FOO在这里指代的是定义ForeignKey的Model的名字，采用小写形式。例：</p>
<pre>
>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # 返回与b相关联的所有blog

>>> b.entry_set.filter(headline__contains='Lennon')  # 返回一个QuerySet
>>> b.entry_set.count()
</pre>
<p>可以在定义ForeignKey的时候使用related_name来手动指定这个manager的名字， 例：</p>
<p>把示例代码中的定义ForeignKey的部分修改为<code>blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')</code></p>
<pre>
>>> b = Blog.objects.get(id=1)
>>> b.entries.all()

>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()
</pre>
<p>ForeignKey方法介绍</p>
<p>add(obj, obj1, ...)</p>
<p>把这些对象添加到关联对象列表里，也就是与这些对象建立关联关系，返回None</p>
<p>remove(obj, obj1, ...)</p>
<p>把这些对象从关联对象列表里删除，返回None</p>
<p>create(**kwargs)</p>
<p>新建一个对象，save，将这个新的对象添加到关联对象列表里</p>
<p>clear()</p>
<p>清除所有关联对象，删除所有关系</p>
<p>set()</p>
<p>替换关联对象列表</p>
</section>
<section>
<h4>多对多关系</h4>
<p>多对多关系的跨越关系的查询语法跟ForeighKey相同, 例:</p>
<pre>
e = Entry.objects.get(id=3)
e.authors.all() # 返回该条Entry的所有Author
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # 返回author对应的所有entry
</pre>
<p>定义多对多关系时也可以指定related_name</p>
</section>
<section>
<h4>一对一关系</h4>
<p>一对一关系与多对一关系很像，不同点在于，关系的双方都只有一个元素</p>
<pre>
class EntryDetail(models.Model):
    entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
    details = models.TextField()

ed = EntryDetail.objects.get(id=2)
ed.entry # 返回关联的entry对象
</pre>
<p>在一对一关系中，反向查询默认是直接使用类名的，而不是foo_set的形式</p>
<pre>
e = Entry.objects.get(id=2)
e.entrydetail # 
</pre>
<p>需要注意的是，如果关联对象不存在，那么会抛出DoesNotExist这个Exception</p>
</section>
<section>
<h4>其他</h4>
<p>当我们需要针对关系类型的字段进行过滤时，可以使用对象，也可以使用对象的id来作为查询条件。例:</p>
<pre>
Entry.objects.filter(blog=b) # 使用对象
Entry.objects.filter(blog=b.id) # 使用对象的id
Entry.objects.filter(blog=5) # 使用对象id的另外一种写法
</pre>
</section>
